package playing_with_arrays;

import java.lang.reflect.Field;
import java.util.Arrays;

import org.junit.BeforeClass;
import org.junit.Test;
import org.perf4j.StopWatch;
import org.perf4j.log4j.Log4JStopWatch;
import sun.misc.Unsafe;
import util.TestTools;
import static org.junit.Assert.*;

public final class UnsafeTest 
{
	private final StopWatch stopWatch;
	
	private static final int TEST_RUNS      = TestTools.TEST_RUNS;
	private static final int TEST_DATA_SIZE = TestTools.TEST_DATA_SIZE;
	
	private static final Unsafe UNSAFE;
	static
	{
		Field field;
		try 
		{
			field = Unsafe.class.getDeclaredField("theUnsafe");
			field.setAccessible(true);
			UNSAFE = (Unsafe) field.get(null);
			
		} 
		catch (final Exception e) 
		{
			throw new RuntimeException(e);
		} 
	}
	
	
	
	public UnsafeTest()
	{
		this.stopWatch = new Log4JStopWatch();
	}
	
	@BeforeClass
	public static void checkBeforeClass()
	{
		long base       = UNSAFE.arrayBaseOffset(int[].class);
		long indexScale = UNSAFE.arrayIndexScale(int[].class);
		long offset     = base + TEST_DATA_SIZE * indexScale;
		
		assertTrue(offset <= Integer.MAX_VALUE);
	}
	
	
	///////////////////////////////////////////// READ TESTS
	
	@Test
	public void testStandardSequentialArrayRead()
	{
		final String tag = "testStandardSequentialArrayRead";
		
		int[] srcArr;
		
		int count = 0;
		
		for(int r = 0; r < TEST_RUNS; r++)
		{
			srcArr   = TestTools.createTestData();
			
			this.stopWatch.start(tag);
			
			//------------------------------
			for(int i = 0; i < TEST_DATA_SIZE; i++)
			{
				if(srcArr[i] > 0)
				{
					count++;
				}
			}
			//-------------------------------
			
			this.stopWatch.stop(tag);
			
			System.out.println(count);
		}
	}
	
	
	@Test
	public void testStandardRandomArrayRead()
	{
		final String tag = "testStandardRandomArrayRead";
		
		int[] srcArr;
		int[] indexArr;
		int count = 0;
		
		for(int r = 0; r < TEST_RUNS; r++)
		{
			srcArr   = TestTools.createTestData();
			indexArr = TestTools.createRandomAccessIndexArray();
			
			this.stopWatch.start(tag);
			
			//------------------------------
			for(int i = 0; i < TEST_DATA_SIZE; i++)
			{
				if(srcArr[indexArr[i]] > 0)
				{
					count++;
				}
			}
			//-------------------------------
			
			this.stopWatch.stop(tag);
			
			System.out.println(count);
		}
	}
	
	
	@Test
	public void testUnsafeSequentialArrayRead()
	{
		final String tag = "testUnsafeSequentialArrayRead";
		
		int[] srcArr;
		
		int count = 0;
		
		
		final int baseOffset = UNSAFE.arrayBaseOffset(int[].class);
		final int indexScale = UNSAFE.arrayIndexScale(int[].class);
		long address;
		
		for(int r = 0; r < TEST_RUNS; r++)
		{
			srcArr   = TestTools.createTestData();
			address  = baseOffset;
			
			this.stopWatch.start(tag);
			
			//------------------------------
			for(int i = 0; i < TEST_DATA_SIZE; i++)
			{
				if(UNSAFE.getInt(srcArr, (long) address) > 0)
				{
					count++;
				}
				address += indexScale;
			}
			//-------------------------------
			
			this.stopWatch.stop(tag);
			
			System.out.println(count);
		}
	}
	
	
	@Test
	public void testUnsafeRandomArrayRead()
	{
		final String tag = "testUnsafeRandomArrayRead";
		
		int[] srcArr;
		int[] indexArr;
		int count = 0;
		
		final int baseOffset = UNSAFE.arrayBaseOffset(int[].class);
		final int indexScale = UNSAFE.arrayIndexScale(int[].class);
		
		for(int r = 0; r < TEST_RUNS; r++)
		{
			srcArr   = TestTools.createTestData();
			indexArr = TestTools.createRandomAccessIndexArray();
			
			this.stopWatch.start(tag);
			
			//------------------------------
			for(int i = 0; i < TEST_DATA_SIZE; i++)
			{
				if(UNSAFE.getInt(srcArr, (long) (baseOffset + indexArr[i] * indexScale)) > 0)
				{
					count++;
				}
			}
			//-------------------------------
			
			this.stopWatch.stop(tag);
			
			System.out.println(count);
		}
	}

	///////////////////////////////////////////// WRITE TESTS
	
	@Test
	public void testStandardSequentialArrayWrite()
	{
		final String tag = "testStandardSequentialArrayWrite";
		
		int[] arr;
		
		for(int r = 0; r < TEST_RUNS; r++)
		{
			arr = new int[TEST_DATA_SIZE];
			
			this.stopWatch.start(tag);
			
			//------------------------------
			for(int i = 0; i < TEST_DATA_SIZE; i++)
			{
				arr[i] = i;
			}
			//-------------------------------
			
			this.stopWatch.stop(tag);
			
			System.out.println(arr[arr.length - 1]);
		}
	}
	
	
	
	@Test
	public void testStandardRandomArrayWrite()
	{
		final String tag = "testStandardRandomArrayWrite";
		
		int[] arr;
		int[] indexArr;
		
		for(int r = 0; r < TEST_RUNS; r++)
		{
			arr      = new int[TEST_DATA_SIZE];
			indexArr = TestTools.createRandomAccessIndexArray();
			
			this.stopWatch.start(tag);
			
			//------------------------------
			for(int i = 0; i < TEST_DATA_SIZE; i++)
			{
				arr[indexArr[i]] = i;
			}
			//-------------------------------
			
			this.stopWatch.stop(tag);
			
			System.out.println(arr[arr.length - 1]);
		}
	}
	
	
	
	@Test
	public void testUnsafeSequentialArrayWrite()
	{
		final String tag = "testUnsafeSequentialArrayWrite";
		
		int[] arr;
		int[] indexArr;
		
		final int baseOffset = UNSAFE.arrayBaseOffset(int[].class);
		final int indexScale = UNSAFE.arrayIndexScale(int[].class);
		
		
		for(int r = 0; r < TEST_RUNS; r++)
		{
			arr      = new int[TEST_DATA_SIZE];
			indexArr = TestTools.createForwardSequentialAccessIndexArray();
			
			this.stopWatch.start(tag);
			
			//------------------------------
			for(int i = 0; i < TEST_DATA_SIZE; i++)
			{
				UNSAFE.putInt(arr, (long) (baseOffset + indexArr[i] * indexScale), i);
			}
			//-------------------------------
			
			this.stopWatch.stop(tag);
			
			System.out.println(arr[arr.length - 1]);
		}
	}
	
	@Test
	public void testUnsafeOtherSequentialArrayWrite()
	{
		final String tag = "testUnsafeOtherSequentialArrayWrite";
		
		int[] arr;
		
		final int baseOffset = UNSAFE.arrayBaseOffset(int[].class);
		final int indexScale = UNSAFE.arrayIndexScale(int[].class);
		int address;
		
		
		for(int r = 0; r < TEST_RUNS; r++)
		{
			arr      = new int[TEST_DATA_SIZE];
			address  = baseOffset;
			
			this.stopWatch.start(tag);
			
			final int maxAddress = baseOffset + TEST_DATA_SIZE * indexScale;
			
			//------------------------------
	
			while(address <= maxAddress)
			{
				UNSAFE.putInt(arr, (long) address, address);
				address += indexScale;
			}
			//-------------------------------
			
			this.stopWatch.stop(tag);
			
			System.out.println(arr[arr.length - 1]);
		}
	}	
	
	
	@Test
	public void testUnsafeRandomArrayWrite()
	{
		final String tag = "testUnsafeRandomArrayWrite";
		
		int[] arr;
		int[] indexArr;
		
		final int baseOffset = UNSAFE.arrayBaseOffset(int[].class);
		final int indexScale = UNSAFE.arrayIndexScale(int[].class);
		
		
		for(int r = 0; r < TEST_RUNS; r++)
		{
			arr      = new int[TEST_DATA_SIZE];
			indexArr = TestTools.createRandomAccessIndexArray();
			
			this.stopWatch.start(tag);
			
			//------------------------------
			for(int i = 0; i < TEST_DATA_SIZE; i++)
			{
				UNSAFE.putInt(arr, (long) (baseOffset + indexArr[i] * indexScale), i);
			}
			//-------------------------------
			
			this.stopWatch.stop(tag);
			System.out.println(arr[arr.length - 1]);
		}
	}
	
}
